﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Spanner
{
    public partial class UI
    {
        /// <summary>
        /// Array(xs) constructs the JavaScript array [x1, x2, ..., xn] where
        /// x1, x2, ..., xn are the members of xs, in that order.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T[]> Array<T>(IEnumerable<T> xs)
        {
            return (xs == null ? null : xs.ToArray());
        }

        /// <summary>
        /// Array(x1, x2, ..., xn) constructs the JavaScript array [x1, x2, ..., xn].
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T[]> Array<T>(params T[] xs)
        {
            return xs;
        }

        public static Expr<T[]> Array<T>(params Expr<T>[] xs)
        {
            return JSExpr<T[]>("#", new FmtBrackets(xs));
        }

        /// <summary>
        /// Ith(xs, i) is equivalent to the JavaScript xs[i].
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="i"></param>
        /// <returns></returns>
        public static IndexExpr<T> Ith<T>(Expr<T[]> xs, Expr<int> i)
        {
            return new IndexExpr<T> { X = xs, I = i };
        }

        /// <summary>
        /// Concat(xs1, xs2, ..., xsn) is the JavaScript concatenation of xs1, xs2, ..., xsn.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T[]> Concat<T>(params Expr<T[]>[] xs)
        {
            return new FmtExpr<T[]>(
                "Concat",
                new FmtStr("[].concat"),
                new FmtParens(xs)
            );
        }

        /// <summary>
        /// Every(xs, p) is equivalent to the JavaScript xs.every(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Every<T>(Expr<T[]> xs, Expr<Fn<T, bool>> p)
        {
            return JSExpr<bool>("#.every(#)", xs, p);
        }

        /// <summary>
        /// Every(xs, p) is equivalent to the JavaScript xs.every(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Every<T>(Expr<T[]> xs, Expr<Fn<T, int, bool>> p)
        {
            return JSExpr<bool>("#.every(#)", xs, p);
        }

        /// <summary>
        /// Every(xs, p) is equivalent to the JavaScript xs.every(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and its source array xs and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Every<T>(Expr<T[]> xs, Expr<Fn<T, int, T[], bool>> p)
        {
            return JSExpr<bool>("#.every(#)", xs, p);
        }

        /// <summary>
        /// Filter(xs, p) is equivalent to the JavaScript xs.filter(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and returning bool.</param>
        /// <returns></returns>
        public static Expr<T[]> Filter<T>(Expr<T[]> xs, Expr<Fn<T, bool>> p)
        {
            return JSExpr<T[]>("#.filter(#)", xs, p);
        }

        /// <summary>
        /// Filter(xs, p) is equivalent to the JavaScript xs.filter(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and returning bool.</param>
        /// <returns></returns>
        public static Expr<T[]> Filter<T>(Expr<T[]> xs, Expr<Fn<T, int, bool>> p)
        {
            return JSExpr<T[]>("#.filter(#)", xs, p);
        }

        /// <summary>
        /// Filter(xs, p) is equivalent to the JavaScript xs.filter(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and its source array xs and returning bool.</param>
        /// <returns></returns>
        public static Expr<T[]> Filter<T>(Expr<T[]> xs, Expr<Fn<T, int, T[], bool>> p)
        {
            return JSExpr<T[]>("#.filter(#)", xs, p);
        }

        /// <summary>
        /// IndexOf(xs, x) is equivalent to the JavaScript xs.indexOf(x).
        /// IndexOf(xs, x, fromIndex) is equivalent to the JavaScript xs.indexOf(x, fromIndex).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="x"></param>
        /// <param name="fromIndex"></param>
        /// <returns></returns>
        public static Expr<int> IndexOf<T>(Expr<T[]> xs, Expr<T> x, Expr<int> fromIndex = null)
        {
            return ( ((object)fromIndex) == null
                   ? JSExpr<int>("#.indexOf(#)", xs, x)
                   : JSExpr<int>("#.indexOf(#, #)", xs, x, fromIndex)
                   );
        }

        /// <summary>
        /// Join(xs) is equivalent to the JavaScript xs.join().
        /// Join(xs, sep) is equivalent to the JavaScript xs.join(sep).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="sep"></param>
        /// <returns></returns>
        public static Expr<string> Join<T>(Expr<T[]> xs, Expr<string> sep = null)
        {
            return (((object)sep) == null
                   ? JSExpr<string>("#.join()", xs)
                   : JSExpr<string>("#.join(#)", xs, sep)
                   );
        }

        /// <summary>
        /// LastIndexOf(xs, x) is equivalent to the JavaScript xs.lastIndexOf(x).
        /// LastIndexOf(xs, x, fromIndex) is equivalent to the JavaScript xs.lastIndexOf(x, fromIndex).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="x"></param>
        /// <param name="fromIndex"></param>
        /// <returns></returns>
        public static Expr<int> LastIndexOf<T>(Expr<T[]> xs, Expr<T> x, Expr<int> fromIndex = null)
        {
            return ( ((object)fromIndex) == null
                   ? JSExpr<int>("#.lastIndexOf(#)", xs, x)
                   : JSExpr<int>("#.lastIndexOf(#, #)", xs, x, fromIndex)
                   );
        }

        /// <summary>
        /// Length(xs) is equivalent to the JavaScript xs.length.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<int> Length<T>(Expr<T[]> xs)
        {
            return JSExpr<int>("#.length", xs);
        }

        /// <summary>
        /// map(xs, f) is equivalent to the JavaScript xs.map(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking an item x.</param>
        /// <returns></returns>
        public static Expr<U[]> Map<T, U>(Expr<T[]> xs, Expr<Fn<T, U>> f)
        {
            return JSExpr<U[]>("#.map(#)", xs, f);
        }

        /// <summary>
        /// map(xs, f) is equivalent to the JavaScript xs.map(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking an item x and its index i.</param>
        /// <returns></returns>
        public static Expr<U[]> Map<T, U>(Expr<T[]> xs, Expr<Fn<T, int, U>> f)
        {
            return JSExpr<U[]>("#.map(#)", xs, f);
        }

        /// <summary>
        /// map(xs, f) is equivalent to the JavaScript xs.map(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking an item x and its index i and its source array xs.</param>
        /// <returns></returns>
        public static Expr<U[]> Map<T, U>(Expr<T[]> xs, Expr<Fn<T, int, T[], U>> f)
        {
            return JSExpr<U[]>("#.map(#)", xs, f);
        }

        /// <summary>
        /// Pop(xs) is equivalent to the JavaScript xs.pop().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Pop<T>(Var<T[]> xs)
        {
            return JSExpr<T>("#.pop()", xs);
        }

        /// <summary>
        /// Pop(xs) is equivalent to the JavaScript xs.pop().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Pop<T>(Param<T[]> xs)
        {
            return JSExpr<T>("#.pop()", xs);
        }

        /// <summary>
        /// Pop(xs) is equivalent to the JavaScript xs.pop().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Pop<T>(ObsVar<T[]> xs)
        {
            return Pop(Ref(xs));
        }

        /// <summary>
        /// Pop(xs) is equivalent to the JavaScript xs.pop().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Pop<T>(Expr<ObsVarRef<T[]>> xs)
        {
            return JSExpr<T>("#.pop()", xs);
        }

        /// <summary>
        /// Shift(xs) is equivalent to the JavaScript xs.shift().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Shift<T>(Var<T[]> xs)
        {
            return JSExpr<T>("#.shift()", xs);
        }

        /// <summary>
        /// Shift(xs) is equivalent to the JavaScript xs.shift().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Shift<T>(Param<T[]> xs)
        {
            return JSExpr<T>("#.shift()", xs);
        }

        /// <summary>
        /// Shift(xs) is equivalent to the JavaScript xs.shift().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Shift<T>(ObsVar<T[]> xs)
        {
            return Shift(Ref(xs));
        }

        /// <summary>
        /// Shift(xs) is equivalent to the JavaScript xs.shift().
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <returns></returns>
        public static Expr<T> Shift<T>(Expr<ObsVarRef<T[]>> xs)
        {
            return JSExpr<T>("#.shift()", xs);
        }

        /// <summary>
        /// Reduce(xs, f, a) is equivalent to the JavaScript xs.reduce(f, a).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> Reduce<T, U>(Expr<T[]> xs, Expr<Fn<U, T, U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduce(#, #)", xs, f, a);
        }

        /// <summary>
        /// Reduce(xs, f, a) is equivalent to the JavaScript xs.reduce(f, a).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> Reduce<T, U>(Expr<T[]> xs, Expr<Fn<U, T, int, U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduce(#, #)", xs, f, a);
        }

        /// <summary>
        /// Reduce(xs, f, a) is equivalent to the JavaScript xs.reduce(f, a).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i and its source array xs.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> Reduce<T, U>(Expr<T[]> xs, Expr<Fn<U, T, int, T[], U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduce(#, #)", xs, f, a);
        }

        /// <summary>
        /// Reduce(xs, f) is equivalent to the JavaScript xs.reduce(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> Reduce<T>(Expr<T[]> xs, Expr<Fn<T, T, T>> f)
        {
            return JSExpr<T>("#.reduce(#)", xs, f);
        }

        /// <summary>
        /// Reduce(xs, f) is equivalent to the JavaScript xs.reduce(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> Reduce<T>(Expr<T[]> xs, Expr<Fn<T, T, int, T>> f)
        {
            return JSExpr<T>("#.reduce(#)", xs, f);
        }

        /// <summary>
        /// Reduce(xs, f) is equivalent to the JavaScript xs.reduce(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i and its source array xs.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> Reduce<T>(Expr<T[]> xs, Expr<Fn<T, T, int, T[], T>> f)
        {
            return JSExpr<T>("#.reduce(#)", xs, f);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> ReduceRight<T, U>(Expr<T[]> xs, Expr<Fn<U, T, U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduceRight(#, #)", xs, f, a);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> ReduceRight<T, U>(Expr<T[]> xs, Expr<Fn<U, T, int, U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduceRight(#, #)", xs, f, a);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i and its source array xs.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<U> ReduceRight<T, U>(Expr<T[]> xs, Expr<Fn<U, T, int, T[], U>> f, Expr<U> a)
        {
            return JSExpr<U>("#.reduceRight(#, #)", xs, f, a);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> ReduceRight<T>(Expr<T[]> xs, Expr<Fn<T, T, T>> f)
        {
            return JSExpr<T>("#.reduceRight(#)", xs, f);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> ReduceRight<T>(Expr<T[]> xs, Expr<Fn<T, T, int, T>> f)
        {
            return JSExpr<T>("#.reduceRight(#)", xs, f);
        }

        /// <summary>
        /// ReduceRight(xs, f) is equivalent to the JavaScript xs.reduceRight(f).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="xs"></param>
        /// <param name="f">A function taking the accumulated value acc and an item x and its index i and its source array xs.</param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static Expr<T> ReduceRight<T>(Expr<T[]> xs, Expr<Fn<T, T, int, T[], T>> f)
        {
            return JSExpr<T>("#.reduceRight(#)", xs, f);
        }

        /// <summary>
        /// Slice(xs, begin) is equivalent to the JavaScript xs.slice(begin).
        /// Slice(xs, begin, end) is equivalent to the JavaScript xs.slice(begin, end).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static Expr<T[]> Slice<T>(Expr<T[]> xs, Expr<int> begin, Expr<int> end = null)
        {
            return ( ((object)end) == null
                   ? JSExpr<T[]>("#.slice(#)", xs, begin)
                   : JSExpr<T[]>("#.slice(#, #)", xs, begin, end)
                   );
        }

        /// <summary>
        /// Some(xs, p) is equivalent to the JavaScript xs.some(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Some<T>(Expr<T[]> xs, Expr<Fn<T, bool>> p)
        {
            return JSExpr<bool>("#.some(#)", xs, p);
        }

        /// <summary>
        /// Some(xs, p) is equivalent to the JavaScript xs.some(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Some<T>(Expr<T[]> xs, Expr<Fn<T, int, bool>> p)
        {
            return JSExpr<bool>("#.some(#)", xs, p);
        }

        /// <summary>
        /// Some(xs, p) is equivalent to the JavaScript xs.some(p).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xs"></param>
        /// <param name="p">A predicate taking an item x and its index i and its source array xs and returning bool.</param>
        /// <returns></returns>
        public static Expr<bool> Some<T>(Expr<T[]> xs, Expr<Fn<T, int, T[], bool>> p)
        {
            return JSExpr<bool>("#.some(#)", xs, p);
        }

    }
}
